//Copyright 2008 Kristian Gundry And Rune Juhl-Petersen
//This file is part of Plain.

//Plain is free software: you can redistribute it and/or modify
//it under the terms of the GNU Lesser General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//any later version.

//Plain is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU Lesser General Public License for more details.

//You should have received a copy of the GNU Lesser General Public License
//along with Plain.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Reflection;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace Plain.Web.Utilities.FormBinders
{
    /// <summary>
    /// A class with four methods <see cref="BindObjectToControls"/>, <see cref="BindControlsToObject"/>, 
    /// <see cref="BindObjectToPage"/> and <see cref="BindPageToObject"/>
    /// used to bind the values of and object's properties with web <see cref="Control"/>s or web <see cref="Page"/>s that have their
    /// ID property set to the name of the object property.
    /// </summary>
    public class FormBinding : IFormBinding
    {
        #region Private methods

        /// <summary>
        /// Looks for a property name and type on a control and attempts to set it to the value in an object's property 
        /// of the same name.
        /// </summary>
        /// <param name="obj">The object whose properties are being retrieved</param>
        /// <param name="objProperty">The property of the object being retrieved</param>
        /// <param name="control">The control whose ID matches the object's property name.</param>
        /// <param name="controlPropertiesArray">An array of the control's properties</param>
        /// <param name="propertyName">The name of the Control property being set</param>
        /// <param name="type">The correct type for the Control property</param>
        /// <returns>Boolean for whether the property was found and set</returns>
        private bool FindAndSetControlProperty(object obj, PropertyInfo objProperty, Control control, PropertyInfo[] controlPropertiesArray, string propertyName, Type type)
        {
            // iterate through control properties            
            foreach (PropertyInfo controlProperty in controlPropertiesArray)
            {
                // check for matching name and type                
                if (controlProperty.Name == propertyName && controlProperty.PropertyType == type)
                {
                    // set the control's property to the business object property value
                    object val = objProperty.GetValue(obj, null);

                    if (val != null)
                    {
                        controlProperty.SetValue(control, ChangeType(val, type), null);
                    }

                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Looks for a property name and type on a control and attempts to set it to the value in an object's property 
        /// of the same name.
        /// </summary>
        /// <param name="obj">The object whose properties are being set</param>
        /// <param name="objProperty">The property of the object being set</param>
        /// <param name="control">The control whose ID matches the object's property name.</param>
        /// <param name="controlPropertiesArray">An array of the control's properties</param>
        /// <param name="propertyName">The name of the Control property being retrieved</param>
        /// <param name="type">The correct type for the Control property</param>
        /// <returns>Boolean for whether the property was found and retrieved</returns>
        private bool FindAndGetControlProperty(object obj, PropertyInfo objProperty, Control control, PropertyInfo[] controlPropertiesArray, string propertyName, Type type)
        {
            // iterate through control properties                
            foreach (PropertyInfo controlProperty in controlPropertiesArray)
            {
                // check for matching name and type                    
                if (controlProperty.Name == propertyName && controlProperty.PropertyType == type)
                {
                    // set the control's property to the business object property value                        
                    try
                    {
                        object val = controlProperty.GetValue(control, null);
                        if (val != null)
                        {
                            objProperty.SetValue(obj, Convert.ChangeType(val, objProperty.PropertyType), null);
                            return true;
                        }
                    }
                    catch
                    {
                        // the data from the form control could not be converted to objProperty.PropertyType                            
                        return false;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Findes the top master page of the master page hierachy
        /// </summary>
        /// <param name="master">the masterpage</param>
        /// <returns>top master page</returns>
        private MasterPage GetMasterPage(MasterPage master)
        {
            if (master.Master == null)
            {
                return master;
            }
            else
            {
                return GetMasterPage(master.Master);
            }
        }

        /// <summary>
        /// Findes the HtmlForm of a master page
        /// </summary>
        /// <param name="master">the masterpage</param>
        /// <returns>HtmlForm or null</returns>
        private HtmlForm GetHtmlForm(MasterPage master)
        {
            foreach (Control control in master.Controls)
            {
                if (control is HtmlForm)
                {
                    return control as HtmlForm;
                }
            }
            return null;
        }

        /// <summary>
        /// Findes all contentplaceholders 1 niveau down
        /// </summary>
        /// <param name="htmlForm">HtmlForm</param>
        /// <returns>List of contentplaceholders</returns>
        private IList<ContentPlaceHolder> GetContentPlaceholders(HtmlForm htmlForm)
        {
            IList<ContentPlaceHolder> placeholders = new List<ContentPlaceHolder>();
            foreach (Control control in htmlForm.Controls)
            {
                ContentPlaceHolder placeholder = control as ContentPlaceHolder;
                if (placeholder != null)
                {
                    placeholders.Add(placeholder);
                }
            }
            return placeholders;
        }

        #endregion

        #region Protected methods

        /// <summary>
        /// Converts the value to the type specified
        /// </summary>
        /// <param name="value">Object value</param>
        /// <param name="type">Type</param>
        /// <returns>an object covertet to the type specified</returns>
        protected virtual object ChangeType(object value, Type type)
        {
            return Convert.ChangeType(value, type);
        }

        /// <summary>
        /// Handle complex properties when binding an object to control
        /// </summary>        
        /// <param name="list">The list control</param>
        /// <param name="objProperty">Object Property matching the lists id</param>
        /// <returns>Returns true if property handled by HandleComplexPropertiesObjectToControl</returns>
        protected virtual bool HandleComplexPropertiesObjectToControl(Object obj, ListControl list, PropertyInfo objProperty)
        {
            return false;
        }

        /// <summary>
        /// Handle complex properties when binding a control to an object
        /// </summary>        
        /// <param name="list">The list control</param>
        /// <param name="objProperty">Object Property matching the lists id</param>
        /// <returns>Returns true if property handled by HandleComplexPropertiesObjectToControl</returns>
        protected virtual bool HandleComplexPropertiesControlToObject(Object obj, ListControl list, PropertyInfo objProperty)
        {
            return false;
        }

        /// <summary>
        /// Handles Associated labels text value. 
        /// If a label exits with a AssociatedControlId matching the parameter name.
        /// This metode is used to set the text of the label accordingly.
        /// </summary>
        /// <param name="objectType">The type of the object property</param>
        /// <param name="propertyName">The name of the property</param>
        /// <param name="name"></param>
        protected virtual void BindObjectNameToAssociatedControl(Type objectType, string propertyName, Control container)
        {
            return;
        }
        
        #endregion

        #region Public methods

        ///<summary>
        ///Binds an object's properties to a <see cref="Page"/>'s <see cref="Control"/>s where the property name of the object matches the id of the controll.
        ///</summary>
        /// <param name="obj">The object whose properties are being bound to the page Controls</param>
        /// <param name="page">The page the Controls reside in</param>
        public void BindObjectToPage(object obj, Page page)
        {
            if (page.Master != null)
            {
                MasterPage top = GetMasterPage(page.Master);
                HtmlForm htmlForm = GetHtmlForm(top);
                if (htmlForm != null)
                {
                    foreach (ContentPlaceHolder placeholder in GetContentPlaceholders(htmlForm))
                    {
                        BindObjectToControls(obj, placeholder);
                    }
                }
            }
            else
            {
                BindObjectToControls(obj, page);
            }
        }

        /// <summary>
        /// Binds an object's properties to a <see cref="Control"/>s with the same ID as the propery name. 
        /// </summary>
        /// <param name="obj">The object whose properties are being bound to the forms Controls</param>
        /// <param name="container">The control in which the form Controls reside (usually a ContainerControl)</param>
        public void BindObjectToControls(object obj, Control container)
        {
            if (container is ContentPlaceHolder)
            {
                foreach (Control con in container.Controls)
                {
                    if (con is ContentPlaceHolder)
                    {
                        BindObjectToControls(obj, con);
                    }
                }
            }
            if (obj != null)
            {
                // Get the properties of the business object            
                Type objType = obj.GetType();
                PropertyInfo[] objPropertiesArray = objType.GetProperties();

                foreach (PropertyInfo objProperty in objPropertiesArray)
                {
                    Control control = container.FindControl(objProperty.Name);

                    // Handles Associated labels text value.
                    BindObjectNameToAssociatedControl(objType, objProperty.Name, container);

                    // handle ListControls (DropDownList, CheckBoxList, RadioButtonList)           
                    if (control is ListControl)
                    {
                        ListControl listControl = (ListControl)control;
                        //if not a complex property handle (basic types like string, int .. etc)
                        if (!HandleComplexPropertiesObjectToControl(obj, listControl, objProperty))
                        {
                            string propertyValue = objProperty.GetValue(obj, null).ToString();
                            ListItem listItem = listControl.Items.FindByText(propertyValue);
                            if (listItem != null) listItem.Selected = true;
                        }
                    }
                    else if (control != null)
                    {
                        // get the properties of the control                    
                        Type controlType = control.GetType();
                        PropertyInfo[] controlPropertiesArray = controlType.GetProperties();

                        // test for common properties                    
                        bool success = false;

                        // start looking for text, since this is the most used property.
                        success = FindAndSetControlProperty(obj, objProperty, control, controlPropertiesArray, "Checked", typeof(bool));

                        if (!success)
                            success = FindAndSetControlProperty(obj, objProperty, control, controlPropertiesArray, "SelectedDate", typeof(DateTime));

                        if (!success)
                            success = FindAndSetControlProperty(obj, objProperty, control, controlPropertiesArray, "Value", typeof(String));

                        if (!success)
                            FindAndSetControlProperty(obj, objProperty, control, controlPropertiesArray, "Text", typeof(String));
                    }
                }
            }
        }

        /// <summary>
        /// Binds the <see cref="Page"/>'s <see cref="Control"/>s values to your business object where the property name of the object matches the id of the controll.
        /// </summary>
        /// <param name="obj">The object whose properties are being bound to the page.</param>
        /// <param name="page">The page the Controls reside in.</param>
        public void BindPageToObject(object obj, Page page)
        {
            if (page.Master != null)
            {
                MasterPage top = GetMasterPage(page.Master);
                HtmlForm htmlForm = GetHtmlForm(top);
                if (htmlForm != null)
                {
                    foreach (ContentPlaceHolder placeholder in GetContentPlaceholders(htmlForm))
                    {
                        BindControlsToObject(obj, placeholder);
                    }
                }
            }
            else
            {
                BindControlsToObject(obj, page);
            }
        }

        /// <summary>
        /// Binds the <see cref="Control"/>s values to your business object.
        /// </summary>
        /// <param name="obj">The object whose properties are being bound to Control values</param>
        /// <param name="container">The control in which the form Controls reside (usually a ContainerControl)</param>
        public void BindControlsToObject(object obj, Control container)
        {
            if (container is ContentPlaceHolder)
            {
                foreach (Control con in container.Controls)
                {
                    if (con is ContentPlaceHolder)
                    {
                        BindControlsToObject(obj, con);
                    }
                }
            }
            if (obj != null)
            {
                // Get the properties of the business object            	
                Type objType = obj.GetType();
                PropertyInfo[] objPropertiesArray = objType.GetProperties();
                
                foreach (PropertyInfo objProperty in objPropertiesArray)
                {
                    Control control = container.FindControl(objProperty.Name);
                    if (control is ListControl)
                    {
                        ListControl listControl = (ListControl)control;
                        //if not a complex property handle (basic types like string, int .. etc)
                        if (!HandleComplexPropertiesControlToObject(obj, listControl, objProperty))
                        {
                            if (listControl.SelectedItem != null)
                                objProperty.SetValue(obj, Convert.ChangeType(listControl.SelectedItem.Value, objProperty.PropertyType), null);
                        }
                    }
                    else if (control != null)
                    {
                        // get the properties of the control                    
                        Type controlType = control.GetType();
                        PropertyInfo[] controlPropertiesArray = controlType.GetProperties();

                        // test for common properties                    
                        bool success = false;
                        success = FindAndGetControlProperty(obj, objProperty, control, controlPropertiesArray, "Checked", typeof(bool));

                        if (!success)
                            success = FindAndGetControlProperty(obj, objProperty, control, controlPropertiesArray, "SelectedDate", typeof(DateTime));

                        if (!success)
                            success = FindAndGetControlProperty(obj, objProperty, control, controlPropertiesArray, "Value", typeof(String));

                        if (!success)
                            success = FindAndGetControlProperty(obj, objProperty, control, controlPropertiesArray, "Text", typeof(String));
                    }
                }
            }
        }

        #endregion
    }
}